import { AnimationFrame, Stage, Vector3 } from 'babylon-lib/index';
import { CombineRig } from '../../skinned-animate/combine-rig';
import { Quaternion } from '@babylonjs/core/Maths/math.vector';
import { CharacterController, INPUT_TYPE } from './character-controller/character-controller';
import { CreateHandleTo, IHandle, RemoveHandle } from 'babylon-lib/data/data-handle';
import { AbstractInteractiveObject, IAbstractInteractiveObjectData, IAbstractInteractiveObjectOptions } from '../abstract-interactive-object';


interface IAbstractCharacterOptions extends IAbstractInteractiveObjectOptions {
}

interface IAbstractCharacterData extends IAbstractInteractiveObjectData {
}


/**
 * 角色类交互对象抽象类,如玩家、NPC等
 * 加载模型、面朝向
 */
abstract class AbstractCharacter extends AbstractInteractiveObject {
    private _tq0 = Quaternion.Identity();
    private _tq1 = Quaternion.Identity();
    private _tq2 = Quaternion.Identity();
    private _tv0 = new Vector3();
    private _fromAction: CombineRig[] = [];
    private _controller?: CharacterController;
    private _targetAction: CombineRig | undefined | null;
    private _transmitSpeed = 0.02;
    private _transmitPrg = 0;
    private _actionHandles: IHandle<any>[] = [];
    private _transmitReq = {};
    private _dir = new Vector3();
    private _hold = false;
    private _curLoopIndex = -1;

    constructor(stage: Stage, options?: IAbstractCharacterOptions) {
        super(stage, options as IAbstractInteractiveObjectOptions);
    }

    public set controller(controller: CharacterController) {
        this._controller = controller;
        this._controller.onInputChange = (type) => {
            this.onInputChange(type);
        };
    }

    protected abstract onInputChange(type: INPUT_TYPE): void;

    public get controller(): CharacterController {
        return this._controller!;
    }

    public setDirection(direction: Vector3): void {
        direction.y = 0;
        this._dir.copyFrom(direction).normalize();
        this._tv0.copyFrom(this.container.forward).negateInPlace();
        Quaternion.FromUnitVectorsToRef(this._tv0, this._dir, this._tq0);
        this._tq2.normalize();
        const sq = Quaternion.SlerpToRef(this._tq2, this._tq0, 0.1, this._tq1);
        this.container.rotationQuaternion!.multiplyInPlace(sq);
    }

    public onActionChange(func: { (index: number, loop: boolean): void }): IHandle<any> {
        return CreateHandleTo<any>(func, this._actionHandles);
    }
    public removeActionChangeHandle(handle: number | IHandle<any>): void {
        RemoveHandle(handle, this._actionHandles);
    }

    public playAction(index?: number, loop?: boolean): void {
        if (index === undefined) {
            this.combineRig.forEach(c => {
                if (c) {
                    c.stop();
                }
            });
            return;
        }
        this.transmitAction(index, !!loop);
        this._actionHandles.forEach(handle => { handle.callback(index, loop); });
    }

    public dispose(): void {
        this.model.forEach(m => {
            if (m) m.dispose();
        });
        this.container.dispose();
    }

    private transmitAction(index: number, loop: boolean): void {
        /**
         * 记录最近的循环动作，避免非循环动作结束后没有衔接动作
         */
        if (loop) this._curLoopIndex = index;
        if (this._hold) return;

        this._targetAction = this.combineRig[index];
        if (!this._targetAction) return;

        if (!loop) this._targetAction.stop();
        
        this._fromAction.length = 0;
        for (const cr of this.combineRig) {
            if (!cr) continue;
            if (cr.id === this._targetAction.id) continue;
            if (cr.isPlaying) this._fromAction.push(cr);
        }
        this._transmitPrg = 0;

        if (this._fromAction.length === 0) {
            this._targetAction.weight = 1;
        } else {
            AnimationFrame.request((t: number) => {
                const tspd = this._transmitSpeed * t * 0.2;
                this._transmitPrg += tspd;

                this._targetAction!.weight = Math.min(this._targetAction!.weight + tspd, 1);
                this._fromAction.forEach(f => {
                    f.weight = this._transmitPrg < 1 ? Math.max(f.weight - tspd, 0) : 0;
                });
                if (this._transmitPrg >= 1) {
                    return true;
                }
            }, 16, 200, this._transmitReq);
        }
        this.combineRigGroup.play(loop);

        /**
         * 非循环动画强制播放完整，如避免在斜面跳跃一半
         */
        if (!loop) {
            this._hold = true;
            const handle = this._targetAction.onEnd(() => {
                this._hold = false;
                this._targetAction?.removeEndHandle(handle);
                this.playAction(this._curLoopIndex, true);
            });
        }
    }

}


export { AbstractCharacter };
export type {
    IAbstractCharacterOptions,
    IAbstractCharacterData,
};